﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;

namespace FluentETL.Impl
{
    public class TextSource : ISource, IFieldNames
    {
        private TextReader InputReader { get; set; }
        private string InputFile { get; set; }
        private bool UseHeaderFlag { get; set; }
        private int[] FixedColumnWidths { get; set; }
        private Func<string, IEnumerable<string>> LineSplitter { get; set; }
        private Func<string, bool> DoSkipWhen { get; set; }

        public TextSource()
        {
            this.DoSkipWhen = line => line.Length == 0;
        }

        public TextSource From(TextReader reader)
        {
            this.InputReader = reader;
            return this;
        }

        public TextSource From(string file)
        {
            this.InputFile = file;
            return this;
        }

        public TextSource SkipWhen(Func<string, bool> func)
        {
            this.DoSkipWhen = func;
            return this;
        }

        public TextSource ParseAsDelimited(char delimiter, bool useHeader)
        {
            this.LineSplitter = (line => line.Split(delimiter));
            this.UseHeaderFlag = useHeader;
            return this;
        }

        public TextSource ParseAsDelimited(char[] delimiters, bool useHeader)
        {
            this.LineSplitter = (line => line.Split(delimiters));
            this.UseHeaderFlag = useHeader;
            return this;
        }

        public TextSource ParseAsFixedColumns(int[] colWidths, bool useHeader)
        {
            this.FixedColumnWidths = colWidths;
            this.LineSplitter = (line =>
            {
                IList<string> values = new List<string>();
                int ptr = 0;
                for (int idx = 0; idx < this.FixedColumnWidths.Length; idx++)
                {
                    int w = this.FixedColumnWidths[idx];
                    if (ptr + w >= line.Length)
                        values.Add(line.Substring(ptr).TrimEnd());
                    else
                        values.Add(line.Substring(ptr, w).TrimEnd());
                    ptr += w;
                    if (ptr >= line.Length)
                    {
                        break;
                    }
                }
                return values;
            });
            this.UseHeaderFlag = useHeader;
            return this;
        }

        public TextSource ParseWith(Func<string, IEnumerable<string>> lineSplitter, bool useHeader)
        {
            this.LineSplitter = lineSplitter;
            this.UseHeaderFlag = useHeader;
            return this;
        }

        public IEnumerable<string> GetFieldNames()
        {
            if (this.FixedColumnWidths != null)
            {
                if (this.UseHeaderFlag)
                {
                    // Read the first line
                    using (StreamReader rdr = new StreamReader(this.InputFile))
                    {
                        string line;
                        do
                        {
                            line = rdr.ReadLine();
                        } while (line != null && this.DoSkipWhen(line));
                        if (line == null)
                            throw new InvalidDataException("Header line is empty");
                        return from h in this.LineSplitter(line)
                               select h.TrimEnd();
                    }
                }
                else
                {
                    return from n in Enumerable.Range(0, this.FixedColumnWidths.Length)
                           select "Field" + n;
                }
            }

            if (this.InputFile != null && this.LineSplitter != null)
            {
                if (this.UseHeaderFlag)
                {
                    // Read the first line
                    using (StreamReader rdr = new StreamReader(this.InputFile))
                    {
                        string line;
                        do
                        {
                            line = rdr.ReadLine();
                        } while (line != null && this.DoSkipWhen(line));
                        if (line == null)
                            throw new InvalidDataException("Header line is empty");
                        return this.LineSplitter(line);
                    }
                }
                else
                {
                    // Read the whole file (!)
                    int maxFieldCount = -1;
                    using (StreamReader rdr = new StreamReader(this.InputFile))
                    {
                        string line = rdr.ReadLine();
                        while (line != null)
                        {
                            maxFieldCount = Math.Max(maxFieldCount, this.LineSplitter(line).Count());
                            line = rdr.ReadLine();
                        }
                    }
                    return from n in Enumerable.Range(0, maxFieldCount)
                           select "Field" + n;
                }
            }
            throw new InvalidOperationException("Not enough information to determine field names.");
        }

        void ISource.InTransformContext(ITransform t, Action action)
        {
            action();
        }

        IEnumerable<IRow> ISource.Rows
        {
            get
            {
                if (this.InputReader != null)
                {
                    foreach (IRow r in ReadRows(this.InputReader))
                        yield return r;
                }
                else if (this.InputFile != null)
                {
                    using (StreamReader rdr = new StreamReader(this.InputFile))
                    {
                        foreach (IRow r in ReadRows(rdr))
                            yield return r;
                    }
                }
                else
                    throw new InvalidOperationException("No file/stream set.");
            }
        }

        private IEnumerable<IRow> ReadRows(TextReader rdr)
        {
            string line;
            IList<string> headers = null;
            if (this.UseHeaderFlag)
            {
                do
                {
                    line = rdr.ReadLine();
                } while (line != null && this.DoSkipWhen(line));
                if (line == null)
                    throw new InvalidDataException("Header line is empty");
                headers = LineSplitter(line).ToList();
            }
            while ((line = rdr.ReadLine()) != null)
            {
                if (!this.DoSkipWhen(line))
                {
                    IList<string> tokens = LineSplitter(line).ToList();
                    IDictionary<string, object> values = new Dictionary<string, object>();
                    if (headers != null)
                    {
                        for (int idx = 0; idx < tokens.Count; idx++)
                        {
                            values[headers[idx]] = tokens[idx];
                        }
                    }
                    else
                    {
                        for (int idx = 0; idx < tokens.Count; idx++)
                        {
                            values["Field" + idx] = tokens[idx];
                        }
                    }
                    yield return new DictionaryRow(values);
                }
            }
        }
    }
}